/*++

Copyright (c) Microsoft Corporation. 
 
This source code is subject to terms and conditions of the Microsoft
Public License. A copy of the license can be found in the License.txt
file at the root of this distribution. By using this source code in 
any fashion, you are agreeing to be bound by the terms of the Microsoft
Public License.
 
You must not remove this notice, or any other, from this software.

Module Name:

    SensorBaseDriver.cpp

Abstract:

    This module contains the code that parses the data issued by the sensor.
--*/

#include <stdio.h>
#include <tchar.h>

// This driver is entirely user-mode
__user_code;

#include <atlbase.h>
#include <atlcom.h>
#include <atlcoll.h>
#include <atlstr.h>
#include <strsafe.h>
#include <wudfddi.h>
#include <portabledevicetypes.h> // Required to access IPortableDeviceValues

#include "internal.h"
#include "SensorBaseDriver.h" 
#include "SensorBaseDriver.tmh" // autogenerated file

#include "RS232Connection.h"
#include "RS232Target.h"

#include "WpdObjectProperties.h" //Required to access WpdObjectProperty object

#include <sensors.h> // required for event GUIDs

SensorBaseDriver::SensorBaseDriver() :
    m_cRef(1),
    m_Interval(0),
    m_wszDeviceID(NULL)
{
    m_pConnection = new RS232Connection();
    m_pTarget = new RS232Target();
    m_pObjectProperties = new WpdObjectProperties();
}

SensorBaseDriver::~SensorBaseDriver()
{
}

ULONG __stdcall SensorBaseDriver::AddRef()
{
    InterlockedIncrement((long*) &m_cRef);
    return m_cRef;
}

__drv_at(this, __drv_freesMem(Mem)) 
ULONG __stdcall SensorBaseDriver::Release()
{
    ULONG ulRefCount = m_cRef - 1;

    if (InterlockedDecrement((long*) &m_cRef) == 0)
    {
        delete this;
        return 0;
    }
    return ulRefCount;
}

HRESULT __stdcall SensorBaseDriver::QueryInterface(
    REFIID riid,
    void** ppv)
{
    HRESULT hr = S_OK;

    if(riid == IID_IUnknown)
    {
        *ppv = static_cast<IUnknown*>(this);
        AddRef();
    }
    else
    {
        *ppv = NULL;
        hr = E_NOINTERFACE;
    }
    return hr;
}

RS232Target* SensorBaseDriver::GetRS232Target()
{
    //return &m_pTarget;
    return m_pTarget;
}



/**
 * This method is called to initialize the driver object.
 * This is where the driver establishes a connection with the
 * COM port, creates the IoTarget to forward read/write requests to, and
 * starts the thread which monitors these events.
 */
HRESULT SensorBaseDriver::Initialize(IWDFDevice* pDevice)
{
    HRESULT hr    = S_OK;
    HANDLE  hPort = NULL;

    if (pDevice == NULL)
    {
        hr = E_POINTER;
        return hr;
    }

    // Save the WDF device instance
    m_pWDFDevice = pDevice;
    //m_pObjectProperties.Initialize(this);
    

    hr = m_pConnection->Connect(COM_PORT_NAME, &hPort);

    // Initialize the IoTarget to wrap the opened handle
    if (hr == S_OK)
    {
        hr = m_pTarget->Create(this, pDevice, hPort);
    }

    m_pObjectProperties->Initialize(this);

    return hr;
}


/**
 * This method is called to uninitialize the driver object.
 * This is where the driver disables the connection with the
 * COM port and performs the necessary cleanup.
 */
void SensorBaseDriver::Uninitialize()
{

    m_pTarget->Delete();
    m_pConnection->Disconnect();

    m_pWDFDevice = NULL;
}

/**
 * This method is called to extract the sensor data and interval prop from the raw serial buffer
 * and to post a new reading PnP event if valid data is received
 */

HRESULT SensorBaseDriver::ProcessReadData(__in_bcount(cbData) BYTE* pData, size_t cbData)
{
    HRESULT     hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
    LONGLONG    llSensorReading  = 0; // was originally DWORD but the accelerometers required addt'l bytes
    DWORD       dwUpdateInterval = 0;
    LPWSTR      wszDeviceId = NULL;

    // Parse the serial data
    CHAR  szInterval[INTERVAL_DATA_LENGTH + 1]  = {0};  // last byte is always null
    CHAR* szReading                             = NULL; // placeholder pointer

    const SENSOR_INFO c_SensorInfoTable [] = {
      {'1', COMPASS_DATA_LENGTH},   // Compass
      {'2', SENSIRON_DATA_LENGTH},  // Temp/humidity sensor 
      {'3', FLEX_DATA_LENGTH},      // Flexiforce sensor
      {'4', PING_DATA_LENGTH},      // Ultrasonic ping 
      {'5', PIR_DATA_LENGTH},       // Passive infrared
      {'6', MEMSIC_DATA_LENGTH},    // 2-axis accelerometer 
      {'7', QTI_DATA_LENGTH},       // Light sensor
      {'8', PIEZO_DATA_LENGTH},     // Vibration Sensor 
      {'9', HITACHI_DATA_LENGTH},   // 3-axis accelerometer
    };

	// trace
	CHAR* szData = (CHAR *)malloc(cbData + 1);
	memcpy((void*)szData, (void*)pData, cbData);
	szData[cbData] = '\0'; 
	DoTraceMessage(MYDRIVER_ALL_INFO,L"%!FUNC! cbData = %Id pData = %s", cbData, szData);
	free(szData);

	DoTraceMessage(MYDRIVER_ALL_INFO,L"%!FUNC! DEVICE_ID = %c",pData[DEVICE_ID]);

	
    // Allocate the necessary bytes for collecting the sensor data.
    // We'll examine the DEVICE_ID field of the batched serial data and allocate
    // the necessary bytes accordingly...
    // This is also where we set the sensor identifier and related properties).

	// Check if there is at least one character in the buffer
    if (cbData > DEVICE_ID)
    {
        switch (pData[DEVICE_ID]){
            case '1':
                m_SensorType = COMPASS;
                szReading = (CHAR *)malloc(COMPASS_DATA_LENGTH + 1); // last byte is always null
                wszDeviceId = SENSOR_COMPASS_ID;
                hr = S_OK;
                break;
            case '2':
                m_SensorType = SENSIRON;
                szReading = (CHAR *)malloc(SENSIRON_DATA_LENGTH + 1); // last byte is always null
                wszDeviceId = SENSOR_TEMPERATURE_ID;
                hr = S_OK;
                break;
            case '3':
                m_SensorType = FLEX;
                szReading = (CHAR *)malloc(FLEX_DATA_LENGTH + 1); // last byte is always null
                hr = S_OK;
                break;
            case '4':
                m_SensorType = PING;
                szReading = (CHAR *)malloc(PING_DATA_LENGTH + 1); // last byte is always null
                wszDeviceId = SENSOR_PROXIMITY_ID;
                hr = S_OK;
                break;
            case '5':
                m_SensorType = PIR;
                szReading = (CHAR *)malloc(PIR_DATA_LENGTH + 1); // last byte is always null
                wszDeviceId = SENSOR_PIR_ID;
                hr = S_OK;
                break;
            case '6':
                m_SensorType = MEMSIC;
                szReading = (CHAR *)malloc(MEMSIC_AMOUNT_TO_READ + 1); // last byte is always null
                wszDeviceId = SENSOR_DUAL_AXIS_ID;
                hr = S_OK;
                break;
            case '7':
                m_SensorType = QTI;
                szReading = (CHAR *)malloc(QTI_DATA_LENGTH + 1); // last byte is always null
				//TODO: handle
                hr = S_OK;
                break;
            case '8':
                m_SensorType = PIEZO;
                szReading = (CHAR *)malloc(PIEZO_DATA_LENGTH + 1); // last byte is always null
				//TODO: handle
                hr = S_OK;
                break;
            case '9':
                m_SensorType = HITACHI;
                szReading = (CHAR *)malloc(HITACHI_DATA_LENGTH + 1); // last byte is always null
                wszDeviceId = SENSOR_THREE_AXIS_ID;
                hr = S_OK;
                break;
            default:
                break;
        }
        // This member-variable is used when we post
        // the state-change event in the RS232Target
        this->m_wszDeviceID = wszDeviceId;
    }
    
    if ((hr == S_OK) && (szReading == NULL))
    {
        hr = E_OUTOFMEMORY;
    }
    
    // Ensure we have sufficient input buffer size, and, if we do
    // process the data for the given sensor. 
    if ((cbData >= (INTERVAL_DATA_LENGTH + MIN_DATA_LENGTH)) && hr == S_OK)
    {
        int k = 0;

        for (int i=0; i<ARRAYSIZE(c_SensorInfoTable); i++)
        {
            if (pData[DEVICE_ID] == c_SensorInfoTable[i].deviceID)
            {
                memcpy((void*)szReading, (void*)pData, c_SensorInfoTable[i].dataLength);
				szReading[c_SensorInfoTable[i].dataLength] = '\0';  // ensure null termination
				k = _snscanf_s(szReading, c_SensorInfoTable[i].dataLength, "%lld", &llSensorReading);
                memcpy((void*)szInterval, (void*)(pData+c_SensorInfoTable[i].dataLength), INTERVAL_DATA_LENGTH);

				DoTraceMessage(MYDRIVER_ALL_INFO,L"%!FUNC! - szReading = %s",szReading); 
				DoTraceMessage(MYDRIVER_ALL_INFO,L"%!FUNC! - szInterval = %s",szInterval); 
				break;
            }
        }

        // Process the Interval data
        szInterval[INTERVAL_DATA_LENGTH] = '\0';  // ensure null termination
        _snscanf_s(szInterval, ARRAYSIZE(szInterval), "%d", &dwUpdateInterval);
        m_pObjectProperties->SetSamplingRate((ULONG)dwUpdateInterval);
        m_Interval = dwUpdateInterval;

         // TODO: Need to check results of _snscanf_s and memcpy above and use this to set the HRESULT.
         hr = S_OK;
    }

    // Free the bytes allocated with malloc()
    free(szReading);

    if (hr == S_OK)
    {
		DoTraceMessage(MYDRIVER_ALL_INFO, L"%!FUNC! - llSensorReading = %lld", llSensorReading); 
		DoTraceMessage(MYDRIVER_ALL_INFO, L"%!FUNC! - m_Interval = %d" ,m_Interval); 

		// First update the sensor reading value
		if (m_pObjectProperties != NULL)
        {
            hr = m_pObjectProperties->UpdateSensorReadingForObject(wszDeviceId, llSensorReading);
        }  

		// Post the Updated PnP event so applications receive the notification
        hr = PostSensorReadingEvent(wszDeviceId);
    }

    return hr;
}


/**
 * This method is called to send a device event with 
 * the new sensor data
 */

HRESULT SensorBaseDriver::PostSensorReadingEvent(LPCWSTR wszDeviceID)
{
    HRESULT     hr       = S_OK;

    CComPtr<IPortableDeviceValues> spEventParams;
    CComPtr<IPortableDeviceKeyCollection> spKeys;
    CComPtr<IPortableDeviceValuesCollection> spValuesCollection;

    if(SUCCEEDED(hr))
    {
         hr = spValuesCollection.CoCreateInstance(CLSID_PortableDeviceValuesCollection);
    }

    if(SUCCEEDED(hr))
    {            
          // Use the Ddi class to create the key collection.
          hr = m_pObjectProperties->OnGetSupportedDataFields((LPWSTR)wszDeviceID, &spKeys);
    }

    if(SUCCEEDED(hr))
    {
           hr = m_pObjectProperties->OnGetDataFields((LPWSTR)wszDeviceID, spKeys, &spEventParams);
    }

    if(SUCCEEDED(hr))
    {
           // Add the values to the collection.
           hr = spValuesCollection->Add(spEventParams);
    }

    if(SUCCEEDED(hr))
    {
           // Add the data event property key.
           hr = spEventParams->SetGuidValue(SENSOR_EVENT_PARAMETER_EVENT_ID, SENSOR_EVENT_DATA_UPDATED);
    }

    if((SUCCEEDED(hr)) && (m_spSensorCXT != NULL))
    {
        // Post the event.
        hr = m_spSensorCXT->PostEvent((LPWSTR)wszDeviceID, spValuesCollection);
    }

    if (m_spSensorCXT == NULL)
        hr = S_FALSE;

    return hr;
}








